## 1.1 Точка на плоскости

Написать класс PointXY, у которого есть атрибуты x и y. Пример:

```python
# Без конструктора
p0 = PointXY()
p4 = PointXY()
p0.x = 1
p0.y = 5
p4.x = 8
p4.y = 7

# С конструктором:
p1 = PointXY(1, 3)
p2 = PointXY(4, 5)
```

Сделать методы
а) расстояние от начала координат до этой точки

``` python
d0 = p0.distance_from_zero()
```

б) расстояние от точки до другой точки

``` python
d = p0.distance_to(p4)
```

Забыли геометрию?
Расстояние от нуля:
![\Large \sqrt{x^{2}+y^{2}}](https://latex.codecogs.com/svg.latex?\Large&space;\sqrt{x^{2}+y^{2}})

Расстояние между двумя точками
![\Large \sqrt{{(x_{1}-x_{2})}^{2}+{(y_{1}-y_{2})}^{2}}](https://latex.codecogs.com/svg.latex?\Large&space;\sqrt{{(x_{1}-x_{2})}^{2}+{(y_{1}-y_{2})}^{2}})

<details><summary>Как взять корень квадратный</summary>
корень квадратный можно получив либо просто возведя число в степень 0.5 (x ** 0.5), либо воспользовавшись встроенной функцией возведения в степень [pow()](https://docs.python.org/3/library/functions.html#pow) (pow(x, 0.5)), либо функцией [sqrt()](https://docs.python.org/3/library/math.html#math.sqrt) из модуля math (math.sqrt(x))
</details>

в) Дополнить класс PointXY, чтобы можно было указывать координаты при создании


``` python
class PointXY:
    def __init__(self, ...):
        # ...

# ...

p1 = PointXY(3, 4)
```

г) Добавить функции сравнения двух точек == != с помощью методов [```__eq__()```, ```__ne__()```](https://docs.python.org/3/reference/datamodel.html#object.__lt__)

```python
print(p1 == p2)
print(p1 != p2)
```

д) Добавить арифметические функции. Сложение точек - как векторная сумма (сумма иксов и игреков) [```__add__()```](https://docs.python.org/3/reference/datamodel.html#object.__add__). Умножение точки на число - x и y умножаются на это число, функция ```__mul__()```. 
Все арифметические функции будут создавать новый объект типа ```PointXY```.

```python
p3 = p1 + p2
p4 = p1 * 0.5
```

г) Унарные операции [```__neg__(), __invert__()```](https://docs.python.org/3/reference/datamodel.html#object.__neg__) - пусть дают новую точку, у которой x и y с противоположным знаком. Также можно сделать ```___abs___()```, пусть это будет длина вектора.

```python
p1 = -p2
p1 = ~p2
print(abs(p1))
```

## 1.2 Автомобиль

Сделать класс ```Car```

```python
c1 = Car()
```

а) У машины должны быть атрибуты
* "сколько бензина в баке"
* "вместимость бака" - сколько максимум влезаем бензина
* "расход топлива на км"

``` python
c1.gas_l = 30
c1.capacity_l = 40
c1.l_per_km = 0.1
```

б) метод "залить столько-то литров в бак"

``` python
c1.fill(5)  # залили 5 литров
```

должна учитываться вместительность бака
если пытаемся залить больше, чем вмещается, то заполняется полностью + print'ом выводится сообщение о лишних литрах

в) метод "проехать сколько-то км"

``` python
c1.ride(50)  # едем 50 км (если хватит топлива) 
```

выведет принт "проехали ... километров"
в результате поездки потратится бензин
если бензина не хватает, машина всё равно едет, пока не кончится бензин

г) добавить атрибут с пробегом, который увеличивается в результате ride

★д) сделать так, чтобы расход топлива увеличивался на 5% каждые 1000км.
*Здесь можно либо увеличивать пробег скачкообразно каждые 1000км, можно увеличивать более плавно каждый км, а можно вспомнить школьные интегралы.*



## 1.3 Прямоугольник по двум сторонам
Создать класс прямоугольник:

а) при создании указывается ширина и длина

``` python
r1 = Rect(10, 56)
```

б) методы для площади и периметра

``` python
print(r1.area())  # возвращает площадь
print(r1.perimeter())  # периметр
```

в) масштабирование и поворот

``` python
r1.scale(10) - высота и ширина увеличиваются в 10 раз
r1.scale(0.1) - высота и ширина уменьшаются в 10 раз
r1.rotate() - меняется ширина и высота местами
```

### Дополнительные задания на полиморфизм и magic methods:

г) ```__repr__()``` - отобразить в виде текста

д) ```~r1 (__invert__())``` - длина станет шириной и наоборот; результат операций - новый прямоугольник.

е) ```r1 * 5 (__mul__(), __rmul__())``` - обе стороны станут в 5 раз больше

ё) ```r1 / 5 (__truediv__())``` - обе стороны станут в 5 раз меньше

ж) ```r1 < r2, r1 == r2, r1 <= r1``` и т.п.

[Шесть методов для сравнения:](https://docs.python.org/3/reference/datamodel.html#object.__lt__) ```__lt__(), __gt__(), __le__(), __ge__(), __eq__(), __ne__()```


``` python
def __gt__(self, other):
	# ...
	# return True/False
```

## 1.4 Прямоугольник по двум точкам
На основе предыдущих двух задач (PointXY и Rect) сделать прямоугольник, у которого задаются координаты вершин. Стороны при этом всегда параллельны осям координат, то есть для задания достаточно двух точек (вершин).

а) при создании указываются две любые вершины типа PointXY

``` python
p1 = PointXY(1,2)
p2 = PointXY(5,6)
r2 = Rect(p1, p2)
print(r2.area())
```

(Методы rotate и scale можно не делать)

б) то же самое, но внутри хранить именно нижний левый и верхний правый углы

в) сделать метод, которым можно проверить, лежит ли точка внутри прямоугольника

``` python
print(r1.has_inside(p3)) 
```

г) аналогично пункту в) сделать зеркальный метод у класса PointXY: теперь спрашиваем у точки, лежит ли она внутри указанного прямоугольника

``` python
p1.is_inside(r1)  # находится ли p1 внутри r1
```

## 1.5 Игральные кубики

1. Написать класс Dice - игральный кубик (6 граней, от 1 до 6)
а) ```d1 = Dice()``` - конструктор по умолчанию
б) Сделать метод roll()  -вернёт число от 1 до 6

``` python
d1 = Dice()
d2 = Dice()
for i in range(10):
	score = d1.roll() + d2.roll()
	print(score)
```
<details><summary>Подсказка про random</summary>
можно использовать randrange(1, 7) из модуля random, либо choice() из того же модуля
</details>

б) Сделать метод ```show_stats()```, который показывает статистику по каждому из кубиков (сколько раз какая грань выпала за историю его существования). Каким образом хранить, обновлять и отображать данные, выберите самостоятельно.

в) то же, что и в пункте б), но с общей статистикой по всем кубикам.

г) сделать так, чтобы можно было создавать не только стандартный 6-гранный кубик, но и любой другой; статистику можно хранить как в пункте б)

![Кубики](img/dice.jpg)

``` python
d1 = Dice()  # обычный кубик
d2 = Dice({1, 2, 3, 4})
d3 = Dice(range(0, 91, 10))
```

## 1.6 Прямоугольники для вёрстки
Прямоугольники очень удобно использовать для вёрстки графических интерфейсов, т.к. можно поделить экран на прямоугольные фрагменты и вписать в них какие-нибудь виджеты. Особенно удобно, если есть функции, позволяющие трансформировать их. Идея позаимствована из класса ```Rectangle``` библиотеки JUCE (C++), там очень много дополнительных методов помимо упомянутых в задании, можно посмотреть здесь: [ссылка](https://docs.juce.com/master/classRectangle.html).
Взять прямоугольник из задачи 1.4 и дополнить методами:

а) Получить новый прямоугольник, отрезав от него кусок. Исходный прямоугольник остаётся неизменным.
``` python
p1 = PointXY(100, 200)
p2 = PointXY(100, 400)
r1 = Rect(p1, p2)

r2 = r1.with_trimmed_left(50)  # Новый прямоугольник, у которого слева отрезали 50 пикселей, но правая граница осталась на той же координате
# аналогично right, top, bottom
```

б) Отрезать от прямоугольника кусок. В return попадает отрезанный кусок, исходный прямоугольник лишается отрезанного фрагмента.
``` python
r3 = r1.remove_top(100)  # r3 - верхние 100 пикселей прямоугольника r1, а в r1 - то, что осталось. Новый r1 и r3 вместе составляют то, чем раньше был r1
# аналогично другие 3 стороны
```

в) Переместить прямоугольник. Исходный прямоугольник остаётся прежним, у нового прямоугольника те же размеры, но новые координаты.
``` python
r4 = r1.translated(10, -5)  # На 10 пикселей правее и 5 пикселей выше
r5 = r1.with_right_x(100)  # Тот же размер, но правая граница - на координате 100
r6 = r1.with_center(PointXY(500, 500))  # Центр теперь в точек (500, 500), размеры те же.
```

## 1.7 Банк

Есть банк с некоторым количеством счетов. У каждого счёта есть номер карты, имя владельца, валюта, баланс в этой валюте, статус счёта. Сделать класс ```Account```, отражающий операции с этими счетами.

Карта            | Имя                 | Валюта | Баланс    | Статус
-----------------|---------------------|--------|----------:|----------
5484226324895551 | Эльвира Горшкова    | RUB    | 3567345.0 | Platinum
5448408873345429 | Инга Фролова        | USD    |   56350.0 | Regular
5324581138345126 | Любомир Соломин     | USD    |  365735.0 | Platinum
5421208288953119 | Людмила Комарова    | RUB    |   67356.0 | Regular
5590611534175608 | Юрий Ефимов         | RUB    |   35673.0 | Regular
4556708889466643 | Архип Арефьев       | RUB    |   73567.0 | Regular
4716635219613023 | Иосиф Егоров        | RUB    |   36700.0 | Regular
4929563260314825 | Гульназ Долгорукова | USD    |    3300.0 | Regular
5272618635686909 | Ева Чеснокова       | RUB    |   34500.0 | Regular
5429623632161254 | Исмаил Ульянов      | USD    |  345600.0 | Platinum
5335945769667297 | Злата Евсеева       | RUB    |   56780.0 | Platinum
5370309129828741 | Евпраксия Балашова  | USD    |   56740.0 | Regular
5293791986097508 | Захар Евдокимов     | RUB    |   56335.0 | Regular
5493009986150328 | Митрофан Балашов    | USD    |     700.0 | Regular
5588740321224313 | Рудольф Сергеев     | RUB    |   58670.0 | Regular


<details><summary>В виде списка кортежей</summary>

``` python
[(5484226324895551, 'Эльвира Горшкова', 'RUB', 3567345.0, 'Platinum'), (5448408873345429, 'Инга Фролова', 'USD', 56350.0, 'Regular'), (5324581138345126, 'Любомир Соломин', 'USD', 365735.0, 'Platinum'), (5421208288953119, 'Людмила Комарова', 'RUB', 67356.0, 'Regular'), (5590611534175608, 'Юрий Ефимов', 'RUB', 35673.0, 'Regular'), (4556708889466643, 'Архип Арефьев', 'RUB', 73567.0, 'Regular'), (4716635219613023, 'Иосиф Егоров', 'RUB', 36700.0, 'Regular'), (4929563260314825, 'Гульназ Долгорукова', 'USD', 3300.0, 'Regular'), (5272618635686909, 'Ева Чеснокова', 'RUB', 34500.0, 'Regular'), (5429623632161254, 'Исмаил Ульянов', 'USD', 345600.0, 'Platinum'), (5335945769667297, 'Злата Евсеева', 'RUB', 56780.0, 'Platinum'), (5370309129828741, 'Евпраксия Балашова', 'USD', 56740.0, 'Regular'), (5293791986097508, 'Захар Евдокимов', 'RUB', 56335.0, 'Regular'), (5493009986150328, 'Митрофан Балашов', 'USD', 700.0, 'Regular'), (5588740321224313, 'Рудольф Сергеев', 'RUB', 58670.0, 'Regular')]
```
</details>



<details><summary>В виде списка словарей</summary>

``` python
[{'card': 5484226324895551, 'name': 'Эльвира Горшкова', 'currency': 'RUB', 'balance': 3567345.0, 'status': 'Platinum'}, {'card': 5448408873345429, 'name': 'Инга Фролова', 'currency': 'USD', 'balance': 56350.0, 'status': 'Regular'}, {'card': 5324581138345126, 'name': 'Любомир Соломин', 'currency': 'USD', 'balance': 365735.0, 'status': 'Platinum'}, {'card': 5421208288953119, 'name': 'Людмила Комарова', 'currency': 'RUB', 'balance': 67356.0, 'status': 'Regular'}, {'card': 5590611534175608, 'name': 'Юрий Ефимов', 'currency': 'RUB', 'balance': 35673.0, 'status': 'Regular'}, {'card': 4556708889466643, 'name': 'Архип Арефьев', 'currency': 'RUB', 'balance': 73567.0, 'status': 'Regular'}, {'card': 4716635219613023, 'name': 'Иосиф Егоров', 'currency': 'RUB', 'balance': 36700.0, 'status': 'Regular'}, {'card': 4929563260314825, 'name': 'Гульназ Долгорукова', 'currency': 'USD', 'balance': 3300.0, 'status': 'Regular'}, {'card': 5272618635686909, 'name': 'Ева Чеснокова', 'currency': 'RUB', 'balance': 34500.0, 'status': 'Regular'}, {'card': 5429623632161254, 'name': 'Исмаил Ульянов', 'currency': 'USD', 'balance': 345600.0, 'status': 'Platinum'}, {'card': 5335945769667297, 'name': 'Злата Евсеева', 'currency': 'RUB', 'balance': 56780.0, 'status': 'Platinum'}, {'card': 5370309129828741, 'name': 'Евпраксия Балашова', 'currency': 'USD', 'balance': 56740.0, 'status': 'Regular'}, {'card': 5293791986097508, 'name': 'Захар Евдокимов', 'currency': 'RUB', 'balance': 56335.0, 'status': 'Regular'}, {'card': 5493009986150328, 'name': 'Митрофан Балашов', 'currency': 'USD', 'balance': 700.0, 'status': 'Regular'}, {'card': 5588740321224313, 'name': 'Рудольф Сергеев', 'currency': 'RUB', 'balance': 58670.0, 'status': 'Regular'}]
```
</details>

а) Сделать конструктор класса. Сохранить все 5 параметров (номер карты, имя, валюта, баланс, статус) в атрибутах класса.

``` python
a1 = Account(5484226324895551, 'Эльвира Горшкова', 'RUB', 3567345.0, 'Platinum')
a2 = Account(5448408873345429, 'Инга Фролова', 'USD')  # 0 долларов на счету, статус "Regular" по умолчанию
```

По желанию: сделать вместо этого конструктор, который принимает кортеж, словарь или именованные аргументы
``` python
a3 = Account((5493009986150328, 'Митрофан Балашов', 'USD', 700.0, 'Regular\n'), (5588740321224313, 'Рудольф Сергеев', 'RUB', 58670.0, 'Regular'))
a3 = Account({'card': 5588740321224313, 'name': 'Рудольф Сергеев', 'currency': 'RUB', 'balance': 58670.0, 'status': 'Regular'})
a3 = Account(card= 5588740321224313, name='Рудольф Сергеев', currency='RUB', balance=58670.0, status='Regular')  # Через **kwargs
```

б) Сделать операцию "Зачислить из другого банка". Операция будет эмулировать перевод из чужого банка в наш, то есть деньги будут приходить из ниоткуда, и ни у кого не вычитаться. Пока без комиссий.

``` python
a1.receive(100)  # Увеличить счёт на 100 единиц валюты, в которой этот счёт
```

в) Аналогично предыдущему пункту, сделать операцию "отправить в другой банк". Здесь уже требуется следить за балансом: если на счету недостаточно средств для отправления, то операция должна быть отклонена, то есть состояние счёта не изменится.

``` python
a1.send(500)  # Уменьшить счёт на 500 денежных единиц, если они там есть
```

_Примечание. В случаях, когда операция может произойти или быть отклонена, бывает уместно делать методы, которые возвращают некий код результата. Например, можно сделать, чтобы функция возвращала число "0", когда всё хорошо, и "1", когда недостаточно средств, и другие числа, если операция отклоненя по другим причинам. Либо можно вызывать исключения через ```raise``` и обрабатывать их в дальнейшем._ 

г) Добавить комиссии на переводы:

Входящие переводы

* в долларах — 3% от суммы перевода, но не менее $200 и не более суммы перевода

* в рублях — 3%, минимум 15 000₽ и не более суммы перевода

Исходящие переводы

* В рублях — 1%

* В долларах — 3%, при этом минимальная сумма перевода — $20 000.

д) Сделать переводы между пользователями банка. Здесь примечательно, что мы пишем метод класса Account, один из аргументов которого — другой экземпляр этого же класса. Переводы пока должны работать только между счетами с одинаковыми валютами.

``` python
a1.transfer(500, a2)  # Перевести со счёта a1 на a2 500 дененжных единиц
```

е) Дополнить предыдущий пункт, разрешив переводить между счетами с разными валютами. В процессе перевода валюта отправителя сконвертируется в валюту получателя. В качестве аргумента будем указывать сумму в валюте _отправителя_. Курс можно взять с сайта Центробанка РФ или произвольный.

ж) Ограничить исходящие переводы для пользователей со статусом "Regular". Максимальная сумма исходящего перевода - 50 000₽ для рублёвых счетов и $1000 — для долларовых. У пользователей со статусом "Platinum" ограничений не будет.

з) (По желанию) Добавить опциональный аргумент "Включить комиссию в сумму перевода"

``` python
# Комиссия исходящего платежа 3%

# "за счёт отправителя"
a3.send(100, false)  # со счёта спишется 103₽, до получателя дойдёт ровно 100₽, комиссия 3₽

# "за счёт получателя"
a3.send(100, true)  # до получателя дойдёт 97.09₽, комиссия 3% = 2.91₽, в сумме - ровно 100₽
```

и) Завести ещё один атрибут класса, являющийся списком всех операций по счёту. Исходящие переводы будут отрицательными числами, входящие - положительными. В списке будут храниться только суммы операций.

```python
# Пример
[1000, -200, 12.34, -300, 6000]
```

# 2. Наследование

## 2.1 Квадрат
сделать класс Square - квадрат, который наследуется от пр-ка

``` python
class Square(Rect):
    def __init__(self, size):
        # ...

    # больше никаких методов писать не нужно
    
sq = Square(5)  # Квадрат 5x5
print(sq.area())
print(sq.perimeter())
```

## 2.2 Телевизор
Сделать класс Display, унаследованный от Rect из задачи 1.2 ("Прямоугольник по двум сторонам")
а) В конструкторе указывается диагоняль в дюймах, ширина и высота в пикселях
``` python
samsung_mon = Display(19, 1920, 1080)  # 19", 1920x1080 пикселей
```

б) Должны вычисляться площадь и периметр (в дюймах или метрах - как хотите). А ещё монитор может поворачиваться на 90 градусов. Методы должны достаться от прямоугольника.

``` python
samsung_mon.area() # площадь в кв. дюймах, либо в кв. м
samsung_mon.perimeter()
samsung_mon.rotate()
```

в) Монитор должен сообщать количество точек на дюйм и количество пикселей на экране

``` python
samsung_mon.get_dpi()  # количество точек на дюйм
samsung_mon.get_pixel_num()  # сколько мегапикселей у экрана
print(samsung_mon.get_resolution_px())  # разрешение в пикселях в виде кортежа: (1920, 1080)
```

## 2.3 Список покупок
Сделать класс ```ShoppingList```. Он будет почти таким же, как и словарь, но его можно будет умножать на число и складывать с другим таким же объектом:

``` python
soup = {'onion': 1, 'potato': 2, 'water': 2.5, 'meat': 0.5}
salad = {'potato': 2, 'meat': 0.3, 'mayonaise': 0.1, 'peas': 200}
soup + 2 * salad  # словари так не умеют :(
```

а) Сделать конструктор, который создаёт объект из словаря:

``` python
soup = ShoppingList({'onion': 1, 'potato': 2, 'water': 2.5, 'meat': 0.5})
```

<details><summary>Подсказка про конструктор, если решаете задачу через наследование</summary>
Если в конструктор обычного словаря передать другой словарь (например, x = dict({'a': 1, 'b': 2}), то коструктор скопирует в создаваемый словарь всё содержимое исходного. Такое поведение в рамках этой задачи нас полностью устраивает, поэтому свой конструктор делать вообще не нужно, мы получим его в наследство.)
</details>

б) Если ещё не изучали "magic methods", сделать методы ```multiply()``` и ```add()```, которые работают следующим образом:

``` python
# Создаст новый объект типа ShoppingList, в котором все числа (количество или вес ингредиентов) вдвое больше исходного.
two_salads = salad.multiply(2)

# Создаст новый объект типа ShoppingList, в котором отражен перечень ингредиентов, нужный для изготовления двух блюд одновременно.
# Если какие-то ингредиенты салата используются и в супе (например, potato и meat), то соответвующие им числа складываются
# Если какие-то ингредиенты салата в супе не используются (mayonaise, peas), то они всё равно заносятся в новый словарь.
soup_and_salad = soup.add(salad)
```

в) Реализовать методы ```__add__()```, ```__mul__()```, ```__rmul__()```, ```__repr__()```.

``` python
soup_sl = ShoppingList({'onion': 1, 'potato': 2, 'water': 2.5, 'meat': 0.5})
salad_sl = ShoppingList({'potato: 2', 'meat': 0.3, 'mayonaise': 0.1, 'peas': 200})
shopping = soup_sl + salad_sl * 2
print(shopping)
```

Класс можно реализовать как с помощью композиции:

``` python
class ShoppingList:
	def __init__(self, d):
		self.ingredients = d
        # ...
```

так и с помощью наследования от словаря:

``` python
class ShoppingList(dict):
	def __init__(self, d):
        # ...
```

Дополнительно (по желанию):

1. Дополнить класс "прямоугольник" двумя методами:
r1, r2 - объекты Rect
а) r1.is_larger_than(r2) - фактически "r1 > r2" True, если площадь r1 больше площади r2
б) r1.is_equal(r2) - равны ли пр-ки - если равны их стороны
True/False

2. Дополнить класс PointXY методами:
а) __eq__, __ne__ - сравнение == != - на основе совпадения координат
б) __invert__(self), __neg__(self)
p5 = ~p1
p6 = -p2
координаты уножатся на -1 и там, и там
вернуть нужно новую PointXY
в) __abs__(self) -> float - модуль - длина отрезка между началом координат и этой точкой
г) __add__(self, other) -> PointXY
p3 = p1 + p2 - сумма иксов и игреков
д) __mul__(self, other) -> PointXY - умножить точку на число - просто умножаются координаты на это число и получается новая точка
https://docs.python.org/3/reference/datamodel.html

## 2.4 Итерабельное целое число

2. Написать класс SuperInt
* по возможности сделать через арифметику, а не строки

``` python
class SuperInt(int):
	# ...

x = SuperInt(5687654)
```

а) спрашивать длину числа
https://docs.python.org/3/reference/datamodel.html#object.__len__

``` python
print(len(x)) # 7
```

б) спрашивать, есть ли цифра в числе
https://docs.python.org/3/reference/datamodel.html#object.__contains__

``` python
print(2 in x) # False
print(5 in x) # True

```

в) спросить цифру по номеру разряда (считаем с младшего разряда)

``` python
x = SuperInt(5687654)
print(x[0]) # 4
print(x[6]) # 5
print(x[-1]) # 5
```

## 2.5 Список в стиле numpy
Дополнить список list таким образом, чтобы можно было передавать список индексов в качестве индекса

а) a = [12, 34, 354, 56, 23] # SuperList
print(a[[0, -1, 1]]) # [0, 23, 34] # list

б) a > 40
[False, False, True, ...]

в) индекс T/F как маска

a[[False, False, True, ...]]

## 2.6 Словарь для временнЫх данных

4. Сделать словарь, в котором ключ - это дата. Дату можно указывать в разных форматах в виде текста, словарь должен их все понимать:

 * дд-мм-гггг
 * дд.мм.гггг
 * дд/мм/гггг
 * гггг-мм-дд
 * и другие по желанию

Должно выполняться

``` python
d = DateDict()

d['25.03.2021'] = 354
d['26 mar 2021'] = 548
d['2021-03-27'] = 456

print(d['2021-03-26']) # 548, хотя при создании ключ был в другом формате
```

<details><summary>Подсказка о том, что можно использовать</summary>
Для хранения даты удобно использовать модуль datetime. Объекты типа datetime.date могут быть ключами словаря. Также полезен модуль dateutil, в нём есть парсер текстовых дат, который как раз создаёт объекты datetime.

<pre>
py -3 -m pip install python-dateutil
</pre>

<pre>
from dateutil import parser
d = '2021-03-26' # str
d2 = parser.parse(d).date() # datetime.date

def __getitem__(self, key):
	if type(str) is str:
		key_dt = parser.parse(key).date() # 
		super().__setitem__(self, key_dt)
</pre>
</details>

## 2.7 Римские цифры

Сделать класс Roman, которая происходит от int

Dec| Rom  | Dec | Rom  | Dec  | Rom
---|------|-----|------|------|-----
 1 | I    |  11 | XI   |  200 | CC
 2 | II   |  20 | XX   |  300 | CCC
 3 | III  |  30 | XXX  |  400 | CD
 4 | IV   |  40 | XL   |  500 | D
 5 | V    |  50 | L    |  600 | DC
 6 | VI   |  60 | LX   |  700 | DCC
 7 | VII  |  70 | LXX  |  800 | DCCC
 8 | VIII |  80 | LXXX |  900 | CM
 9 | IX   |  90 | XC   | 1000 | M
10 | X    | 100 | C    | 1001 | MI

а) конструктор из целых чисел
``` python
x = Roman('MMCI')
y = Roman('IX')
```

б) отображение в виде строки
``` python
print(x) # 'MMCI'
```

в) вся арифметика (+*/- и > < != ==....) - с другими римскими цифрами и int'ами
``` python
z = x + y
g = x + 10
```
Можно в результате операций получать int (проще) или Roman (посложнее)

г) конструктор из строк

``` python
x2 = Roman.from_str('MMCI')
y2 = Roman.from_str('IX')
```

Подсказка 1:
удобнее рассматривать числа вида IV, IX, XV и т.п. как самотоятельные цифры наряду с I V X C ...

Подсказка 2:
<pre>
d = {
	1: 'I',
	4: 'IV',
	5: 'V',
	9: 'IX',
	10: 'X',
	40: 'XL',
	50: 'L',
	90: 'XC',
	100: 'C',
	400: 'CD',
	500: 'D',
	900: 'CM',
	1000: 'M'
}
</pre>

Подсказка 3


# 3 Большая задача про колоду карт

## 3.1 Игральная карта

Сделать класс Card

а) сделать конструктор - число либо буква J/Q/K/A + название масти

``` python
c1 = Card(5, 'spades')
c2 = Card('A', 'hearts')
```

* старшинство - число от 2 до 10, либо 'J', 'Q', 'K', 'A'
* масть - название масти строкой

Символ |  Код
------:|--------
   ♠   |'/u2660'
   ♣   |'/u2663'
   ♥   |'/u2665'
   ♦   |'/u2666'

б) сделать repr

* с1 -> '5♠'
* c2 -> 'A♥'

в) операции сравнения карт, правила будут такие:

* если у карт разные масти, то сравниваются на основе мастей; старшинство мастей такое: ♠ < ♣ < ♦ < ♥

* если масти одинаковые, то смотрим на старшинство (номинал): туз самый старший, двойка самая младшая


<details><summary>В помощь, если не хочется писать руками много словарей</summary>

``` python

valid_values = {2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A'}

weights = {
    2: 101,
    3: 102,
    4: 103,
    5: 104,
    6: 105,
    7: 106,
    8: 107,
    9: 108,
    10: 109,
    'J': 110,
    'Q': 111,
    'K': 112,
    'A': 113
    }

valid_values = {'hearts', 'spades', 'clubs', 'diamonds'}

weights = { # ♠<♦<♣<♥
    'hearts': 1,
    'clubs': 2,
    'diamonds': 3,
    'spades': 4,
}

symbols = {
    'hearts': '\u2665',
    'clubs': '\u2663',
    'diamonds': '\u2666',
    'spades': '\u2660',
}

```

</details>

г) Написать функцию или метод, которая генерирует случайную карту

``` python
c = Card.random()
print(c)
```

д) Написать функцию (не метод) ```generate_deck()``` - не принимает аргументов, возвращает список list из 52 карт, отсортированных

``` python
deck = Card.generate_deck()
for card in deck:
    print(card)
```

е) Написать метод __hash__(), который позволит использовать карту в качестве элемента множества (set) и ключа словаря (dict). У двух одинаковых карт хэш должен быть одинаковый, у разных - разный. 
<details><summary>Подсказка по методу вычисления хэша</summary>
Хэш можно либо вычислить "с нуля", например, на основе имеющихся весов rank/suit, либо свести к вычислению хэша чего-то готового, например, строки или кортежа.</details>

## 3.2 Колода карт
Написать класс Deck, который будет отвечать за создание и работу колоды карт. Должен выполняться следующий код:

``` python 
# Создали колоду, уже отсортированная, 52 карты
d = Deck()

# Перемешали
d.shuffle()

# Достали 5 карт
hand = d.draw(5)
print(hand)
``` 

а) Реализовать с помощью композиции: внутри класса Deck лежит список list карт типа card, Deck ни от чего не наследуется.

б) Реализовать с помощью наследования: класс Deck сам является подклассом списка list, в нём же лежат карты card.

Совет: в этой задаче удобно класс Card и все вспомогательные классы и функции можно положить в отдельный файл (например, cards.py) и подключать как модуль, т.к. в них мы изменения вносить уже не будем:

``` python
from cards import Card, Rank, Suit

class Deck: # композиция
    def __init__(self):
    # ...
```

## 3.2 Карточная игра
Написать на основе этой колоды класс Game, который будет отвечать за игру в блэк джек пользователя с крупье (компьютером).

# 4. Большая задача про базу данных автомобилей
Создать класс для базы автомобилей

auto.json, auto_small.json

а) загрузить данные из файла

б) Сделать клас Car, который конструируется из словаря dict вида

``` python
{
    "kmage": 72500,
    "model": "Porsche 911",
    "generation": "VI (997) Carrera S",
    ....
    "power": 355,
    "fuel": "Бензин"
}
```
	
в) У автомобиля сделать метод ```matches()```, которому передаётся словарь с критериями поиска. Метод должен вернуть ```True``` если машина удовлетворяет всем критериям или ```False``` если не подходит хотя бы по одному.
	
```python
# Численные значения
print(car1.match({'year': 2005})) # точно указанный год
print(car1.match({'year': (2005, 2010)})) # кортеж - диапазон лет, пусть начало и конец включается

# Нечисленные значения
print(car1.match({'color': 'красный'})) # точно указанный цвет
print(car1.match({'color': {'красный', 'бордовый', 'малиновый'}})) # множество - машина должна быть одного из этих цветов

# Несколько критериев - машина должна удовлетворять всем, чтобы вернуть True
print(car1.match({'year': (2005, 2010), 'color': {'красный', 'бордовый', 'малиновый'}, 'transmission': 'автомат'}))
```

г) Сделать класс Selection, в котором лежит список list автомобилей (класс Car)

д) Сделать метод - фильтр. Возвращает объект типа selection, но с другим набором авто.

``` python
selection.filter({'kmage': (50000, 100000), 'year': '2018'})
```

е)
Сделать сортировку в Selection по указанному в виде строки признаку. Результат - объект типа selection, но отсортированный как надо.

``` python
cars4 = cars1.sort_by('year')
sorted(cars2.records, key = lambda car: car.price)
sorted(cars2.records, key = lambda car: getattr(car, 'price'))
sorted(cars2.records, key = lambda car: getattr(car, by))
```

https://docs.python.org/3/library/functions.html#sorted


е) Сделать ф-ию get_unique(), которая вернёт множество уникальных значений в выборке по указанному полю
res = set() 

